<template>
  <div>
    <div style="margin-bottom:12px;">
      <n-button attr-type="button" type="info" @click="onEdit(null)">新增</n-button>
      &nbsp;<n-button type="warning" @click="onExport">导出配置</n-button>
      &nbsp;<n-button type="warning">导入配置<input id="uploadFile" style="position: absolute;top:0;left:0;width:100%;height:100%;opacity:0;" type="file" @change="LoadFile"/></n-button>
      &nbsp;<n-button type="success" title="只针对打包" @click="onReset">刷新</n-button>
      &nbsp;<n-button type="error" @click="handleConfirm">初始化配置</n-button>
      &nbsp;<n-button type="error" @click="allGitPull">一键拉取</n-button>
    </div>
    <n-data-table
      :columns="state.columns"
      :data="state.data"
      :row-key="(row) => row.id"
      default-expand-all
    />
    <n-modal v-model:show="state.showModal">
      <n-card style="width:80vw;">
      <n-form ref="formRef" :model="state.form" :rules="rules">
        <n-row :gutter="12">
          <n-col :span="12">
              <n-form-item label="序号">
                  <n-input-number v-model:value="state.form.id"  placeholder="请输入序号"/>
              </n-form-item>
          </n-col>
          <n-col :span="12">
              <n-form-item path="name" label="名称">
                  <n-input v-model:value="state.form.name" @keydown.enter.prevent placeholder="请输入名称" />
              </n-form-item>
          </n-col>
          <n-col :span="6">
              <n-form-item path="cmd" label="执行命令">
                  <n-input v-model:value="state.form.cmd" @keydown.enter.prevent placeholder="请输入执行命令" />
              </n-form-item>
          </n-col>
          <n-col :span="6">
              <n-form-item path="params" label="启动参数">
                  <n-input v-model:value="state.form.params" @keydown.enter.prevent placeholder="请输入执行参数空格" />
              </n-form-item>
          </n-col>
          <n-col :span="6">
              <n-form-item path="build" label="打包参数">
                  <n-input v-model:value="state.form.build" @keydown.enter.prevent placeholder="请输入打包参数空格" />
              </n-form-item>
          </n-col>
          <n-col :span="6">
              <n-form-item label="端口号">
                  <n-input-number v-model:value="state.form.port"  placeholder="请输入端口号"/>
              </n-form-item>
          </n-col>
          <n-col :span="12">
              <n-form-item path="dir" label="执行目录">
                  <n-input v-model:value="state.form.dir" @keydown.enter.prevent placeholder="请输入执行目录" />
                  <n-button @click="OnDir">选择目录</n-button>
              </n-form-item>
          </n-col>
          <n-col :span="12">
              <n-form-item label="打包后压缩">
                  <n-switch v-model:value="state.form.zip" />
              </n-form-item>
          </n-col>
          <n-col :span="24">
              <n-form-item path="remark" label="备注">
                <n-input v-model:value="state.form.remark" @keydown.enter.prevent placeholder="请输入备注" />
              </n-form-item>
          </n-col>
          <n-col :span="24">
              <n-button @click="state.showModal = false" attr-type="button" type="info" >取消</n-button>
              &nbsp;
              <n-button attr-type="button" type="success" @click="onSubmit">保存</n-button>
          </n-col>
      </n-row>
        </n-form>
      </n-card>
    </n-modal>
  </div>
</template>
<script setup lang="tsx">
import { reactive, ref, onMounted } from 'vue'
import { NButton, useMessage, FormInst, useDialog } from 'naive-ui'
import type { DataTableColumns } from 'naive-ui'
import { CreateOutline,TrashOutline } from '@vicons/ionicons5'
const message = useMessage()
const dialog = useDialog()

const props = defineProps({
  start: {
    type: Function,
    default: () => {},
  },
  gitPull: {
    type: Function,
    default: () => {},
  },
  stop: {
    type: Function,
    default: () => {},
  },
  build: {
    type: Function,
    default: () => {},
  },
  log: {
    type: Function,
    default: () => {},
  },
  dir: {
    type: Function,
    default: () => {},
  },
})

const cacheKey = "TASK_LIST";

const setIntervalIds = {};

interface Song {
  id: number
  port: number
  name: string
  cmd: string
  params: string
  build: string
  dir: string
  zip: boolean
  remark: string
}

const formRef = ref<FormInst | null>(null)

const initDefaultForm = {
    id: 1,
    name: '项目1',
    cmd: 'npm',
    params: 'run dev',
    build: "run build",
    dir: './demo/test',
    port: 5173, // 端口号
    zip: true, // 压缩
    remark: '',
}

const rules = {
    name: {
        required: true,
        message: '请输入名称',
        trigger: 'blur'
    },
    cmd: {
        required: true,
        message: '请输入执行命令',
        trigger: 'blur'
    },
    params: {
        required: true,
        message: '请输入执行参数',
        trigger: 'blur'
    },
    build: {
        required: true,
        message: '请输入打包参数',
        trigger: 'blur'
    },
    dir: {
        required: true,
        message: '请输入执行目录',
        trigger: 'blur'
    },
}

// 列
function createColumns(): DataTableColumns<Song> {
  return [
    {
      expandable: () => true,
      type: 'expand',
      renderExpand: (row) => {
        if (!state.startIdLog[row.id]) state.startIdLog[row.id] = [];
        if (!state.buildIdLog[row.id]) state.buildIdLog[row.id] = [];
        if (!state.errIdLog[row.id]) state.errIdLog[row.id] = [];
        if (!state.gitIdLog[row.id]) state.gitIdLog[row.id] = [];
        
        const startLogs = state.startIdLog[row.id];
        const buildLogs = state.buildIdLog[row.id];
        const errLogs = state.errIdLog[row.id];
        const gitLogs = state.gitIdLog[row.id];
        
        return (
          <div>
             <n-tabs type="line" animated>
              <n-tab-pane name="start" tab="运行日志"
              v-slots={{
                tab: () => <div style="display: flex;align-items: center;">运行日志&nbsp;<n-icon onClick={() => state.startIdLog[row.id] = []}><TrashOutline /></n-icon></div>,
              }}
              >
                <div style="max-height: 300px;overflow-y: auto;">
                {
                  startLogs.map((item) => {
                    const urlPattern = /(https?:\/\/[^\s]+)/g;
                    const matchs = item.match(urlPattern)
                    let a = [];
                    if (matchs && matchs.length) {
                      a = matchs.map(m => {
                        return (<span style="color:red;">{m}</span>)
                      })
                      item = item.replace(urlPattern, "")
                    }
                    return(<p>{item} {a}</p>)
                  })
                }
                </div>
              </n-tab-pane>
              <n-tab-pane name="build" tab="打包日志"
              v-slots={{
                tab: () => <div style="display: flex;align-items: center;">打包日志&nbsp;<n-icon onClick={() => state.buildIdLog[row.id] = []}><TrashOutline /></n-icon></div>,
              }}
              >
                <div style="max-height: 300px;overflow-y: auto;">
                {
                  buildLogs.map((item) => {
                    return(<p>{item}</p>)
                  })
                }
                </div>
              </n-tab-pane>
              <n-tab-pane name="err" tab="错误日志"
              v-slots={{
                tab: () => <div style="display: flex;align-items: center;">错误日志&nbsp;<n-icon onClick={() => state.errIdLog[row.id] = []}><TrashOutline /></n-icon></div>,
              }}
              >
                <div style="max-height: 300px;overflow-y: auto;">
                  {
                    errLogs.map((item) => {
                      return(<p>{item}</p>)
                    })
                  }
                </div>
              </n-tab-pane>
              <n-tab-pane name="git" tab="git日志"
              v-slots={{
                tab: () => <div style="display: flex;align-items: center;">git日志&nbsp;<n-icon onClick={() => state.gitIdLog[row.id] = []}><TrashOutline /></n-icon></div>,
              }}
              >
                <div style="max-height: 300px;overflow-y: auto;">
                  {
                    gitLogs.map((item) => {
                      return(<p>{item}</p>)
                    })
                  }
                </div>
              </n-tab-pane>
            </n-tabs>
          </div>
        )
      }
    },
    {
      title: '序号',
      key: 'id'
    },
    {
      title: '名称',
      key: 'name'
    },
    {
      title: '目录',
      key: 'dir'
    },
    {
      title: '压缩',
      key: 'zip',
      render(row,key:number) {
        return (
          <n-tag>{row.zip ? '是' : '否'}</n-tag>
        )
      }
    },
    {
      title: '命令',
      key: 'cmd'
    },
    {
      title: '参数',
      key: 'params',
      render(row,key:number) {
        return (
          <n-space vertical>
            <n-tag>{row.params}</n-tag>
            {row.build && (<n-tag>{row.build}</n-tag>)} 
          </n-space>
        )
      }
    },
    {
      title: '端口',
      key: 'port'
    },
    {
      title: '命令端',
      key: 'action',
      render(row,key:number) {
        const options = [
          {
            label: '启动',
            key: 'start',
          },
          {
            label: '停止',
            key: 'stop',
          },
          {
            label: '打包',
            key: 'build',
          },
          {
            label: '拉取',
            key: 'gitPull',
          },
        ]

        const select = (key:string) => {
          if (key === 'start') {
            row.action = "启动"
            Start(row)
          } else if (key === 'stop') {
            row.action = "停止"
            Stop(row)
          } else if (key === 'build') {
            row.action = "打包"
            Build(row)
          } else if (key === 'gitPull') {
            row.action = "拉取"
            GitPull(row)
          }
        }

        return (
        <div>
          <n-dropdown size="small" trigger="hover" options={options} onSelect={select}>
            <n-button size="small">{row.action || '命令'}</n-button>
          </n-dropdown>
        </div>
        )
      }
    },
    {
      title: '操作',
      key: 'handle',
      render(row,key:number) {
        return (
        <div>
          <NButton strong={true} type="info" size="small" onClick={() => onEdit(row)}><n-icon><CreateOutline/></n-icon></NButton>
          <NButton strong={true} type="error" size="small" onClick={() => onDel(row)}><n-icon><TrashOutline/></n-icon></NButton>
        </div>
        )
      }
    }
  ]
}

// 默认的任务 用于测试
const initTaskData = [
  {...initDefaultForm},
  { name: "ping百度", cmd: "ping", params: "www.baidu.com", build: "", dir: "", port: 0, id: 111, zip: false },
  { name: "ping谷歌", cmd: "ping", params: "www.google.com", build: "", dir: "", port: 0, id: 2222, zip: false }
]

// 数据
const state = reactive({
  loading: false,
  showModal: false,
  form: {...initDefaultForm},
  data: JSON.parse(JSON.stringify(initTaskData)),
  columns:  createColumns(),
  // 启动运行日志
  startIdLog: {},
  // 打包运行日志
  buildIdLog: {},
  // 错误日志
  errIdLog: {},
  // git日志
  gitIdLog: {},
})

onMounted(() => {
  const taskListStr = window.localStorage.getItem(cacheKey)
  if (taskListStr) {
    state.data = JSON.parse(taskListStr).map(item => {
      item.port = item.port ? item.port - 0 : 0;
      return item;
    })
    message.success("加载配置成功")
  }
})

// 启动
const Start  = (row: Song) => {
  message.info(`启动中... ${row.name}`)
  const post = JSON.stringify(row)
  props.start && props.start(post, (result:string)=> {
    message.success("启动成功")
  })
}

// 拉取
const GitPull  = (row: Song) => {
  message.info(`拉取中... ${row.name}`)
  const post = JSON.stringify(row)
  props.gitPull && props.gitPull(post, (result:string)=> {
    message.success("拉取成功")
  })
}

// 停止
const Stop = (row: Song) => {
  message.info(`停止中... ${row.name}`)
  const post = JSON.stringify(row)
  if (setIntervalIds[row.id]) clearInterval(setIntervalIds[row.id]);
  props.stop && props.stop(post, (result:string)=> {
    console.log("result", result)
  })
}

// 打包
const Build = (row: Song) => {
  message.info(`打包中... ${row.name}`)
  const post = JSON.stringify(row)
  state.loading = true;
  setTimeout(() => {
    state.loading = false;
  }, 30 * 1000)
  props.build && props.build(post, (result:string)=> {
    state.loading = false;
    if (!state.buildIdLog[row.id]) state.buildIdLog[row.id] = [];
    state.buildIdLog[row.id].push(result)
  })
}

// 编辑
const onEdit = (row: Song|null) => {
  state.showModal = true;
  state.form =  row && row.id ? row : {...initDefaultForm} as Song;
  if (!row) state.form.id = Math.floor(Math.random() * 1000);
  message.info(`编辑中`)
}

// 删除
const onDel = (row: Song) => {
  state.data = state.data.filter((item) => item.id !== row.id)
  message.info(`删除 ${row.name}`)
}

// 打开目录
const OnDir = () => {
  message.info(`打开目录中...`)
  props.dir && props.dir((res) => {
    state.form.dir = res;
  })
}

// 一键拉取
const allGitPull = () => {
  message.info(`一键拉取中...`)
  for(const i in state.data) {
    const row = state.data[i]
    const post = JSON.stringify(row)
    props.gitPull && props.gitPull(post, (result:string)=> {
      message.success("拉取成功")
    })
  }
}

// 提交
const onSubmit = (e: MouseEvent) => {
    e.preventDefault()
    formRef.value?.validate((errors) => {
        if (errors) return message.error('参数错误')
        message.success("操作成功")
        const form = JSON.parse(JSON.stringify(state.form))
        let key = -1;
        state.data.map((item,k) => {
          if(form.id == item.id) {
            key = k;
          }
        })
        key >= 0 ?  (state.data[key] = form) : state.data.push(form);
        window.localStorage.setItem(cacheKey, JSON.stringify(state.data))
        state.showModal = false
    })
}

const onReset = () => {
  message.info("初始化中...")
  state.data = [];
  state.buildIdLog = {};
  state.startIdLog = {};
  state.errIdLog = {};
  state.gitIdLog = {};
  setTimeout(() => {
    const taskListStr = window.localStorage.getItem(cacheKey)
    if (taskListStr) {
      state.data = JSON.parse(taskListStr)
      message.success("加载配置成功")
    }
  }, 1500)
}

// 导出配置
const onExport = () => {
    window.localStorage.setItem(cacheKey, JSON.stringify(state.data))
    message.success('导出成功')
    exportFileJSON(state.data)
}

const exportFileJSON = (data = {}, filename = 'dataJSON.json') => {
    if (typeof data === 'object') {
      data = JSON.stringify(data, null, 4);
    }
    // 导出数据
    const blob = new Blob([data], { type: 'text/json' }),
      e = new MouseEvent('click'),
      a = document.createElement('a');

    a.download = filename;
    a.href = window.URL.createObjectURL(blob);
    a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
    a.dispatchEvent(e);
}

// 加载配置
function LoadFile() {
  if (document.getElementById("uploadFile").files.length === 0) { return; }
  const file = document.getElementById("uploadFile").files[0];
  //FileReader读取文件内容
  const reader = new FileReader();
  reader.readAsText(file, 'UTF-8');
  reader.onload = function (e) {
      // urlData就是对应的文件内容
      const resultStr = this.result;
      if (!resultStr) return message.error('文件内容为空')
      try {
        const result = JSON.parse(resultStr.toString())
        state.data = result
        window.localStorage.setItem(cacheKey, JSON.stringify(state.data))
        message.success('导入成功')
      } catch(err) {
        message.error("文件内容格式错误")
      }
  };
}

const handleConfirm = () => {
  dialog.warning({
    title: '初始化配置',
    content: '当前配置会丢失建议先导出配置？',
    positiveText: '确认',
    negativeText: '取消',
    onPositiveClick: () => {
      state.data = JSON.parse(JSON.stringify(initTaskData));
      state.buildIdLog = {}
      state.startIdLog = {}
      state.errIdLog = {};
      state.loading = false;
      window.localStorage.setItem(cacheKey, JSON.stringify(state.data))
      message.success('初始化配置成功')
    },
    onNegativeClick: () => {
    }
  })
}

defineExpose({
  changeLog: (res) => {
    const { id ,logType, log } = res;
    if (!state[logType][id]) state[logType][id] = [];
    state[logType][id].push(log);
  },
  changeUpdate: (res) => {
    const { id  } = res;
    const data = state.data;
    data.map(item => {
      if(item.id == id && res.port) {
        item.port = res.port - 0;
      }
    })
    state.data = data;
    window.localStorage.setItem(cacheKey, JSON.stringify(state.data))
  }
})

</script>
